package org.xhy.hrm.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.esotericsoftware.minlog.Log;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.xhy.hrm.asserts.GlobleAssert;
import org.xhy.hrm.constants.MQConstants;
import org.xhy.hrm.domain.KillCourse;
import org.xhy.hrm.domain.KillOrder;
import org.xhy.hrm.domain.KillOrderItem;
import org.xhy.hrm.globle.GlobleException;
import org.xhy.hrm.mapper.KillCourseMapper;
import org.xhy.hrm.service.IKillCourseService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xhy
 * @since 2021-09-29
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {


    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void add(KillCourse killCourse) {
        //判断参数
        GlobleAssert.isNotNull(killCourse.getKillCount(),"课程不能数量不能为空");

        //保存秒杀课程到数据库
        String num = UUID.randomUUID().toString();
        killCourse.setSessionNumber(num);
        //秒杀数
        killCourse.setKillLimit(1);
        //设置状态待发布
        killCourse.setKillStatus(KillCourse.STATUS_WAIT_PUBLISH);
        //创建时间
        killCourse.setCreateTime(new Date());

        //添加
        insert(killCourse);
    }

    /**
     * 1.查询秒杀课程，条件：待发布，近2天的
     * 2.保存到Redis（hash结构）
     * 3.把库存提出来，使用信号量做库存预热
     * 4.把秒杀课程状态修改为，已经发布
     * 5.幂等处理，如果秒杀课程已经发布，不要在发布了
     */
    @Override
    public void publishKillCourse2Redis() {
        //查询秒杀课程
        EntityWrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("kill_status",KillCourse.STATUS_WAIT_PUBLISH);

        Date startTime = new Date();
        Date endTime = DateUtils.addDays(startTime, 2);
        wrapper.between("start_time",startTime,endTime);

        List<KillCourse> killCourses = baseMapper.selectList(wrapper);

        if (killCourses.isEmpty()){
            return;
        }

        for (KillCourse killCourse : killCourses){
            //保存到redis 大key ，小key、小value
            Boolean putIfAbsent = redisTemplate.opsForHash().putIfAbsent("KEY_KILL_COURSES", killCourse.getId().toString(), killCourse);
            if (!putIfAbsent){
                log.warn("发布失败->{}",killCourse);
                continue;
            }
            //3.把库存提出来，使用信号量做库存预热
            Integer killCount = killCourse.getKillCount();
            RSemaphore semaphore = redissonClient.getSemaphore("KILL_STORE:" + killCourse.getId().toString());
            boolean trySetPermits = semaphore.trySetPermits(killCount);
            if (!trySetPermits){
                //如果失败，就删除此条
                redisTemplate.opsForHash().delete("KEY_KILL_COURSES",killCourse.getId().toString());
                log.error("库存发布失败，此数据删除了");
                continue;

            }
            //如果成功.把秒杀课程状态修改为，已经发布
            killCourse.setKillStatus(KillCourse.STATUS_PUBLISH);
            updateById(killCourse);
        };
    }

    @Override
    public KillCourse getOnlineAllKillCourseById(Long id) {
        KillCourse killCourses = (KillCourse)redisTemplate.opsForHash().get("KEY_KILL_COURSES", id.toString());
        return killCourses;
    }

    @Override
    public List getOnlineAllKillCourse() {
        List<Object> killCourses = redisTemplate.opsForHash().values("KEY_KILL_COURSES");
        return killCourses;
    }

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    /**
     * 1.判断
     *  1.1.判断ID不能为空
     *  1.2.等没登录
     *  1.3.是否已经秒杀过：同一个人，同一个课程，不能秒杀多次，除非支付成功
     *  1.4.秒杀时间合法
     *
     * 2.减去库存
     *  2.1.信号量减库存
     *
     * 3.下单
     *  3.1.创建订单号
     *  3.2.准备订单数据
     *  3.3.发送订单消息到MQ
     */
    @Override
    public String kill(Long id) {
        //参数判断
        GlobleAssert.isNotNull(id,"无效秒杀");
        //判断登录了没 TODO 登录
        Long userId = 3L;
        //判断是否已经秒杀过
        Boolean hasKey = redisTemplate.hasKey("KILLLOG:" + userId + ":" + id);
//         GlobleAssert.isFalse(hasKey,"您已经秒杀过了");

        //秒杀时间合法
        KillCourse killCourse = getOnlineAllKillCourseById(id);

        Date now = new Date();
        if (now.before(killCourse.getStartTime()) || now.after(killCourse.getEndTime())){
            throw new GlobleException("请在指定时间抢购");
        }

        //信号量减库存
        RSemaphore semaphore = redissonClient.getSemaphore("KILL_STORE:" + killCourse.getId().toString());
        //减一
        boolean tryAcquire = semaphore.tryAcquire(1);
        GlobleAssert.isTrue(tryAcquire,"商品暂无，下次继续");

        //下单
        KillOrder killOrder = new KillOrder();
        KillOrderItem killOrderItem = new KillOrderItem();
        //订单号
        String orderNo = simpleDateFormat.format(now);
        //组装信息
        killOrder.setOrderNo(orderNo);
        killOrder.setTotalPrice(killCourse.getKillPrice());
        killOrder.setUserId(userId);
        killOrder.setKillCount(1);

        killOrderItem.setKillCourseId(id);
        killOrderItem.setKillCourseName(killCourse.getCourseName());

        killOrder.setItem(killOrderItem);

        //发送订单消息到MQ
        rabbitTemplate.convertAndSend(
            MQConstants.NAME_EXCHANGE_KILL_TOPIC,
            "killcourse.order",
            killOrder
        );
        //记录秒杀记录
        redisTemplate.opsForValue().set("KILLLOG:"+userId+":"+id,"1");

        return orderNo;
    }

    //定时删除过期课程
    @Override
    public void removeKillCourseFromRedis() {
        BoundHashOperations<Object, String, KillCourse> killCourses = redisTemplate.boundHashOps("KEY_KILL_COURSES");

        List<KillCourse> values = killCourses.values();

        Date now = new Date();

        if (values != null){
            values.forEach(killCourse -> {
                if (now.after(killCourse.getEndTime())){
                    killCourse.delete(killCourse.getCourseId().toString());
                }
            });
        }
    }
}
